/******************************************************************************************************
Copyright (C) 2009 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided 
AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, 
MERCHANTABILITY AND FITNESSFOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

// ====================================================================================================
// This file contains several functions for accessing object properties. It is needed by DBPlugin.
// Do not delete or rename the functions below! You can change the function bodies though.
// ====================================================================================================

/*globals
    Application, ASV, Dir, Document, DocumentWindow, mainWindow, File,
    DBDefinition, DBPlugin, DBItem, dbPreSaveHook, dbPrePasteHook, dbPostPasteHook,
    ElViSSpectrum, MassItem, Molecule, MoleculePlugin, molecule, NMRAssignments, NMRProcessing, NMRSpectrum, NMRPlugin, PageItem, Peak, Peaks,
    print, qsTr, serialization, Settings, SpectrumRegion, Str, Text, TextStream,
    ComboBox, Dialog, GroupBox, Label, LineEdit, MessageBox, ToolButton */
/*jslint plusplus: true, indent: 4 */

function dbError(aMessage) {
    "use strict";
    MessageBox.critical(aMessage, MessageBox.Ok);
}

// Create a new text file with the specified contents.
function dbCreateTextFile(aFilePath, aContents) {
    "use strict";
    var file, outStream;
    file = new File(aFilePath);
    file.open(File.WriteOnly);
    outStream = new TextStream(file);
    outStream.writeln(aContents);
    file.close();
}

function dbGetItem(aUuid) {
    "use strict";
    var dw = Application.mainWindow.activeWindow();
    if (dw) {
        return dw.getItem(aUuid);
    }
    return undefined;
}

// Convert the argument safely to a number string the MnDB server can handle.
// Used to handle unusual values for pos / width / intensities in spectrum peaks.
// - "Unsafe" values are treated as 0.
// - isFinite() handles NaN and infinite values
// - MnServer (currently) stores 4 digits after the decimal point. Since r427: 6 digits in total.
function dbSafeNumber(aArg) {
    "use strict";
    return isFinite(aArg) ? parseFloat(aArg).toPrecision(6) : "0";
}

// Limit precision of floats in JSON data. Used as replacer parameter in JSON.stringify().
/*jslint unparam: true */
function dbSafeNumberJSON(key, value) {
    "use strict";
    if (typeof value === 'number') {
        return Number(dbSafeNumber(value));
    }
    return value;
}
/*jslint unparam: false */

// Returns the assignments for the given molecule and nucleus as as peak list suitable for peak searches (PEAKLIST1A)
function dbGetAssignments(aMolecule, aNucleus) {
    "use strict";
    var a, assignments, atom, shift, h, smallest, s, min, max, count, total, peaks, l,
        shifts = [],
        shifts2 = [],
        peaklines = [];
    try {
        assignments = new NMRAssignments(aMolecule);
    } catch (e) {
        // NMRPlugin is not installed
        return "0";
    }
    // Collect chemical shifts:
    for (a = 1; a <= aMolecule.atomCount; a++) {
        atom = aMolecule.atom(a);
        //print("  Atom {0}: {1}".format(a, atom.text));
        if (aNucleus === "13C" && atom.elementSymbol === "C") {
            shift = assignments.chemShift(a, 0);
            if (shift !== undefined) {
                shifts.push(shift);
            }
        } else if (aNucleus === "1H") {
            for (h = 1; h <= 3; h++) {
                shift = assignments.chemShift(a, h);
                //print("    h{0}: {1}".format(h, shift === undefined ? "undefined" : JSON.stringify(shift)));
                if (shift !== undefined) {
                    shifts.push(shift);
                }
            }
        }
    }
    //for (s = 0; s < shifts.length; s++) print("Shift " + s + ": min=" + shifts[s].min + " max=" + shifts[s].max + " shift="+shifts[s].shift);
    // Order shifts:
    while (shifts.length > 0) {
        smallest = 0;
        for (s = 1; s < shifts.length; s++) {
            if (shifts[s].shift < shifts[smallest].shift) {
                smallest = s;
            }
        }
        shifts2.push(shifts[smallest]);
        shifts.splice(smallest, 1);
    }
    shifts = shifts2;
    // Unify overlapping shifts:
    while (shifts.length > 0) {
        min = shifts[0].min;
        max = shifts[0].max;
        total = shifts[0].shift;
        count = 1;
        shifts.splice(0, 1);
        while (shifts.length > 0 && shifts[0].shift >= min && shifts[0].shift <= max) {
            if (shifts[0].min < min) {
                min = shifts[0].min;
            }
            if (shifts[0].max < max) {
                max = shifts[0].max;
            }
            total += shifts[0].shift;
            count++;
            shifts.splice(0, 1);
        }
        peaklines.push("{0} {1} {2} Compound".format(dbSafeNumber(total / count), dbSafeNumber(max - min), count));
    }
    // Format result:
    peaks = "{0}".format(peaklines.length);
    for (l = 0; l < peaklines.length; l++) {
        peaks += "\n" + (l + 1) + " " + peaklines[l];
    }
    return peaks;
}

// Returns all assignments for the given molecule as text
function dbGetAssignmentsAsText(aMolecule) {
    "use strict";
    var a, assignment, atom, h,
        result = "",
        assignments = aMolecule.nmrAssignments();
        //print(JSON.stringify(assignments, null, 4))
    if (assignments) {
        for (a = 0; a < assignments.length; a++) {
            assignment = assignments[a];
            atom = aMolecule.atom(assignment.atom.index);
            h = assignment.atom.h || " ";
            result += "{0}{1}\t{2}\n".format(Str.atomIndexToString(atom.alias, atom.number,  0, false), h, JSON.stringify(assignment.shift));
        }
    }
    return result;
}

// Returns the visible spectrum region converted to ppm units. 
// Used in DBPlugin peaks query and in Rica scripts.
function dbGetVisibleRegionPPM(aSpec) {
    "use strict";
    var fromX, toX, fromY, toY, unitsX, unitsY,
        zoom = aSpec.getScaleLimits(),
        peakunits = "ppm";

    // Get visible spectrum area in ppm units
    unitsX = aSpec.getProperty("axes.horizontal.units");
    if (aSpec.dimCount === 1) {
        fromX = aSpec.unitsToUnits(unitsX, peakunits, zoom.fromX, 1);
        toX = aSpec.unitsToUnits(unitsX, peakunits, zoom.toX, 1);
        return new SpectrumRegion(fromX, toX);
    }
    if (aSpec.dimCount === 2) {
        fromX = aSpec.unitsToUnits(unitsX, peakunits, zoom.fromX, 2);
        toX = aSpec.unitsToUnits(unitsX, peakunits, zoom.toX, 2);
        unitsY = aSpec.getProperty("axes.vertical.units");
        fromY = aSpec.unitsToUnits(unitsY, peakunits, zoom.fromY, 1);
        toY = aSpec.unitsToUnits(unitsY, peakunits, zoom.toY, 1);
        return new SpectrumRegion(fromX, toX, fromY, toY);
    }
    dbError("Unhandled spectrum dimension '{0}' in getVisibleRegionPPM.").format(aSpec.dimCount);
}

// Returns the total spectrum region converted to ppm units.
// Used in DBPlugin peaks query and in Rica scripts.
function dbGetTotalRegionPPM(aSpec) {
    "use strict";
    var region, zoom;
    // Remember current zoom, zoom out
    zoom = aSpec.getScaleLimits();
    aSpec.zoom();
    region = dbGetVisibleRegionPPM(aSpec);
    // Restore zoom
    aSpec.zoom(zoom);
    return region;
}

// mgo 2019-01-21: DEPRECATED (Used in Rica scripts). Use dbGetVisibleRegionPPM instead.
function getVisibleRegionPPM(aSpec) {
    "use strict";
    return dbGetVisibleRegionPPM(aSpec);
}

// mgo 2019-01-21: DEPRECATED (Used in Rica scripts). Use dbGetTotalRegionPPM instead
function getTotalRegionPPM(aSpec) {
    "use strict";
    return dbGetTotalRegionPPM(aSpec);
}

// mgo 2017-05-29: DEPRECATED. Use Peak.typeToString() directly.
function nmrPeakType(aNumber) {
    "use strict";
    if (aNumber === Peak.Compound) {
        return "C";
    }
    if (aNumber === Peak.Artifact) {
        return "A";
    }
    if (aNumber === Peak.Impurity) {
        return "I";
    }
    if (aNumber === Peak.Solvent) {
        return "S";
    }
    if (aNumber === Peak.CS_Ref) {
        return "R";
    }
    if (aNumber === Peak.Quantification_Ref) {
        return "Q";
    }
    return "U";
}

// Returns the peaklist for a spectrum as text suitable for db storage iff the given spectrum has dimension aDim, else "0".
function getNMRPeaks(aNMRSpectrum, aDim) {
    "use strict";
    var peaks, i, dim, peak, spectrum, nmrProcessing,
        result = "0";
    if (aNMRSpectrum.dimCount !== aDim) {
        return result;
    }
    peaks = new Peaks(aNMRSpectrum.peaks());
    
    // If no peaks found, try to pick some. 
    if (peaks.count === 0) {
        //print("No peaks found, trying to pick...");
        spectrum = new NMRSpectrum(aNMRSpectrum);
        nmrProcessing = new NMRProcessing(aNMRSpectrum.proc);
        nmrProcessing.setParameter("PP.Method", aDim === 1 ? "GSD" : "Standard");  // GSD for 1D, Standard for 2D spectra (#13695).
        nmrProcessing.setParameter("PP.AutoClassify", true);
        nmrProcessing.setParameter("PP.Apply", true);
        spectrum.setProperty("peaks.show", false);
        spectrum.processCurSpec(nmrProcessing);
        spectrum.update();
        peaks = new Peaks(spectrum.peaks());
        // Undo our peak picking (#15392, #10506)
        spectrum.setPeaks(new Peaks());
        nmrProcessing.setParameter("PP.Apply", false);
        spectrum.processCurSpec(nmrProcessing);
        spectrum.update();
    }
    // Build the peak list
    result = "{0}".format(peaks.count);
    for (i = 0; i < peaks.count; i++) {
        peak = peaks.at(i);
        result += "\n" + (i + 1) + " ";
        for (dim = 1; dim <= aNMRSpectrum.dimCount; dim++) {
            result += dbSafeNumber(peak.delta(dim)) + " " + dbSafeNumber(peak.width(dim)) + " ";
        }
        result += dbSafeNumber(peak.intensity);
        result += " " + (peak.typeToString() || "None");
        result += " " + (peak.flagsToString() || "None");
        result += " " + (peak.c13Multiplicity || "None");
    }
    //print(result)
    return result;
}

// Returns the peaklist for a ElViS Spectrum as text suitable for db storage.
function dbGetElViSPeaks(aElViSSpectrum) {
    "use strict";
    var i, peak,
        peaks = aElViSSpectrum.activeSpectrumHasPeaks() ? aElViSSpectrum.peaks() : aElViSSpectrum.forceStdPeaks(),
        result = "0";
    if (peaks !== undefined && peaks.length > 0) {
        // Build the peak list
        result = "{0}".format(peaks.length);
        for (i = 0; i < peaks.length; i++) {
            peak = peaks[i];
            result += "\n" + (i + 1) + " " + dbSafeNumber(peak.center) + " " + dbSafeNumber(peak.width) + " " + dbSafeNumber(peak.height);
        }
    }
    return result;
}

// Binning for 1D Arrays
function dbBinned1D(aPoints, aNumBinsX) {
    "use strict";
    var x, bin, converted,
        num_bins = Math.min(aNumBinsX, aPoints.length),
        bin_size = num_bins / aPoints.length,
        result = [];

    if (num_bins <= 0) {
        return aPoints;
    }
    for (x = 0; x < num_bins; x++) {
        result[x] = 0;
    }
    for (x = 0; x < aPoints.length; x++) {
        bin = Math.floor(x * bin_size);
        converted = Number(dbSafeNumber(aPoints[x]));
        //print("x={0} bin={1} val={2} converted={3} points={4} num_bins={5} bin_size={6} oldVal={7}".format(x, bin, aPoints[x], converted, aPoints.length, num_bins, bin_size, result[bin]));
        result[bin] += converted;
    }
    for (x = 0; x < num_bins; x++) {
        result[x] = Number(dbSafeNumber(result[x] * bin_size));
    }
    return result;
}

// Binning for 2D Arrays
function dbBinned2D(aPoints, aNumBinsX, aNumBinsY) {
    "use strict";
    var x, y, bin_x, bin_y,
        num_bins_x = Math.min(aNumBinsX, aPoints.length),
        num_bins_y = Math.min(aNumBinsY, aPoints[0].length),
        bin_size_x = num_bins_x / aPoints.length,
        bin_size_y = num_bins_y / aPoints[0].length,
        result = [];

    if (num_bins_x <= 0 || num_bins_y <= 0) {
        return aPoints;
    }

    for (x = 0; x < num_bins_x; x++) {
        result[x] = [];
        for (y = 0; y < num_bins_y; y++) {
            result[x][y] = 0;
        }
    }

    for (x = 0; x < aPoints.length; x++) {
        bin_x = Math.floor(x * bin_size_x);
        for (y = 0; y < aPoints[x].length; y++) {
            bin_y = Math.floor(y * bin_size_y);
            result[bin_x][bin_y] += aPoints[x][y];
        }
        //print("i={0} bin={1} val={2} points={3} num_bins={4} bin_size={5} oldVal={6}".format(i, bin, aPoints[i], aPoints.length, num_bins, bin_size, result[bin]));
    }

    for (x = 0; x < num_bins_x; x++) {
        for (y = 0; y < num_bins_y; y++) {
            result[x][y] = Number(dbSafeNumber(result[x][y] / (bin_size_x * bin_size_y)));
        }
    }
    return result;
}

// Returns the list of NMR spectrum points as text suitable for db storage.
// Increase aNumBins for higher accuracy but lower speed. aNumBins = -1 means no binning.
function dbGetNMRPoints(aNMRSpectrum, aDim, aNumBins) {
    "use strict";
    var numBins,
        result = {};
    if (aNMRSpectrum.dimCount !== aDim) {
        return "";
    }
    result.limits = aNMRSpectrum.getFullScaleLimits(); // get zoom object of full spectrum: fromX, toX, fromY, toY 
    if (aNMRSpectrum.dimCount === 1) {
        numBins = aNumBins || 100;
        result.numPointsOrig = aNMRSpectrum.count();
        result.points = dbBinned1D(aNMRSpectrum.real("all"), numBins);
        result.numPoints = result.points.length;
        return JSON.stringify(result, dbSafeNumberJSON, 1);
    }
    if (aNMRSpectrum.dimCount === 2) {
        numBins = aNumBins || 30;
        result.numPointsXOrig = aNMRSpectrum.count(1);
        result.numPointsYOrig = aNMRSpectrum.count(2);
        result.points = dbBinned2D(aNMRSpectrum.real("all", "all"), numBins, numBins);
        result.numPointsX = result.points.length;
        result.numPointsY = result.points[0].length;
        //print(result.points);
        return JSON.stringify(result, dbSafeNumberJSON, 1);
    }
    return JSON.stringify("Error: Unhandled spectrum dimension");
}

// Returns the list of ElViSSpectrum points as text suitable for db storage.
// Increase aNumBins for higher accuracy but lower speed. aNumBins = -1 means no binning.
function dbGetElViSPoints(aElViSSpectrum, aNumBins) {
    "use strict";
    var p,
        spectrum = aElViSSpectrum,
        numPoints = spectrum.count(),
        points = [],
        result = {};
    if (spectrum.specCount > 1) {
        numPoints = 0;   // FR #13495
        //spectrum = spectrum.spectra[spectrum.curSpecIndex];
        //numPoints = spectrum.count();
    }
    for (p = 0; p < numPoints; p++) {
        //print (p);
        //print(spectrum.dataPoint(p));
        points[p] = spectrum.dataPoint(p).y;
    }
    result.numPointsOrig = numPoints;
    result.points = dbBinned1D(points, aNumBins || 100);
    result.numPoints = result.points.length;
    return JSON.stringify(result, dbSafeNumberJSON, 1);
}

// Returns the multiplet list for a spectrum as text suitable for db storage iff the given spectrum has dimension aDim, else "0".
// Note: only aDim=1 is supported atm.
function getNMRMultiplets(aNMRSpectrum, aDim) {
    "use strict";
    var i, m, multiplets,
        result = "0";
    if (aNMRSpectrum.dimCount === aDim) {
        multiplets = aNMRSpectrum.multiplets();
        result = "{0}".format(multiplets.count);
        for (i = 0; i < multiplets.count; i++) {
            m = multiplets.at(i);
            //print("\n    multiplet " + (i+1) + ": delta=" + m.delta + " rangeMin=" + m.rangeMin + "rangeMax=" + m.rangeMax);
            result += "\n" + (i + 1)
                   + " " + dbSafeNumber(m.delta)
                   + " " + dbSafeNumber(m.rangeMin)
                   + " " + dbSafeNumber(m.rangeMax)
                   + " " + m.nH
                   + " " + dbSafeNumber(m.integralValue())
                   + " " + m.category
                   + " " + m.jList().toString().replace("JList [", "[").replace(" ", "").replace(" ", "");
        }
    }
    return result;
}

// DEPRECATED: use getNMRpeaks() 
function getGSDPeaks(aItem, aDim) {
    "use strict";
    return getNMRPeaks(aItem, aDim);
}

// Returns the list of integrals for a spectrum as text suitable for db storage iff the given spectrum has dimension aDim, else "0".
function getIntegrals(aNMRSpectrum, aDim) {
    "use strict";
    var integrals, i, integral, dim, pos, width, absValue, relValue,
        result = "0";
    if (aNMRSpectrum.dimCount === aDim) {
        integrals = aNMRSpectrum.integrals();
        result = "{0}".format(integrals.count);
        for (i = 0; i < integrals.count; i++) {
            integral = integrals.at(i);
            result += "\n" + (i + 1) + " ";
            for (dim = 1; dim <= aNMRSpectrum.dimCount; dim++) {
                pos = (integral.rangeMax(dim) + integral.rangeMin(dim)) / 2.0;
                width = integral.rangeMax(dim) - integral.rangeMin(dim);
                result += pos.toPrecision(9) + " " + width.toPrecision(9) + " ";
            }
            absValue = integral.integralValue(1.0);
            relValue = integral.integralValue(integrals.normValue);
            result += dbSafeNumber(absValue) + " " + dbSafeNumber(relValue);
        }
    }
    return result;
}

// Returns the verification result for a spectrum as an object suitable for use in db storage scripts.
// Will return a default object if any of the assumptions (exactly one molecule in document, verification has been done) are not met.
function getVerificationResult(aNMRSpectrum) {
    "use strict";
    var m, mol, verificationResults,
        dw = mainWindow.activeWindow(),
        done = false,
        // Constants
        cInvalidQuality = -10.0,
        cInvalidScore = -10.0,
        cInvalidSignificance = -1.0,
        cInvalidId = 0,
        // Construct a default result object
        result = {};
    result.moleculeId = "";
    result.id = cInvalidId;
    result.result = cInvalidScore;         // deprecated, Issue #7159
    result.quality = cInvalidQuality;
    result.score = cInvalidScore;
    result.significance = cInvalidSignificance;
    result.testsResults = [];
    result.testsResultsAsText = function () { return "0"; };
    result.toString = function () { return "Undefined VerificationResult"; };
    // Check if we have a document
    if (!dw) {
        return result;
    }
    // Check if we have a valid Verify Plugin
    if (ASV === undefined) {
        return result;
    }
    // See if we can get a verification result - try molecules in order of which they appear in document
    for (m = 0; m < dw.itemCount("Molecule") && !done; m++) {
        mol = new Molecule(dw.item(m, "Molecule"));
        verificationResults = ASV.getResults(aNMRSpectrum, mol);
        if (verificationResults !== undefined && verificationResults.length > 0) {
            result = verificationResults[0];
            result.moleculeId = mol.moleculeId;
            done = true;
        }
    }
    // Add on testsResults in text format for db storage
    result.testsResultsAsText = function () {
        var i, s;
        s = "{0}".format(this.testsResults.length);
        for (i = 0; i < this.testsResults.length; i++) {
            s += "\n" +  i
                + "," + this.testsResults[i].id
                + "," + this.testsResults[i].quality.toPrecision(4)        // = score and significance combined
                + "," + this.testsResults[i].result.toPrecision(4)         // = score
                + "," + this.testsResults[i].significance.toPrecision(4);
        }
        return s;
    };
    // Add on toString() utility function for debugging
    result.toString = function () {
        var i, s;
        s = String();
        if (this.moleculeId === "") {
            s = "Undefined ";
        }
        s += "VerificationResult " + this.moleculeId + " (" + this.id + "): " + this.result + " (" + this.significance + ") [";
        for (i = 0; i < this.testsResults.length; i++) {
            s += "(" + i + ":" + this.testsResults[i].id + "," + this.testsResults[i].result + "," + this.testsResults[i].significance + ")";
        }
        s += "]";
        return s;
    };
    //print(result);
    return result;
}

// Builds and returns a string suitable for databse storage
// 2011-06-07: Deprecated -- Use MassSpectrum.dbStore(plotID)
function getMassData(aItem) {
    "use strict";
    var massitem = new MassItem(aItem),
        plot = massitem.plotByType(MassItem.Spectrum),
        result = "0 0\n";
    if (plot) {
        result = massitem.dbStore(plot.id);
        // Begin temporary code
        if (result.indexOf(" 0 0") !== -1) {
            MessageBox.warning("Warning: spectrum contains m/z values of 0.");
        }
        // End temporary code
    }
    return result;
}

// Builds and returns a search query string from a mass spectrum object
function getMassQuery(aUuid) {
    "use strict";
    var minscore, scoring, tolerance, toleranceInDa,
        massitem = new MassItem(dbGetItem(aUuid)),
        settings = new Settings("DBSearch/Settings");
    minscore = settings.value("MinHitQuality/MassItemSearch", 800) / 1000.0;
    scoring = settings.value("MS Search/Scoring", "Purity");
    tolerance = settings.value("MS Search/Tolerance", 0.5);
    toleranceInDa = settings.value("MS Search/ToleranceInDalton", true);
    return massitem.dbQuery(scoring, minscore, tolerance, toleranceInDa);
}

// Convert MassItem.ChromatogramPeakType to string
function massChromatogramPeakType(aNumber) {
    "use strict";
    if (aNumber === MassItem.ChromatogramPeakType.BB) {
        return "BB";
    }
    if (aNumber === MassItem.ChromatogramPeakType.BV) {
        return "BV";
    }
    if (aNumber === MassItem.ChromatogramPeakType.Shoulder) {
        return "Shoulder";
    }
    if (aNumber === MassItem.ChromatogramPeakType.VB) {
        return "VB";
    }
    if (aNumber === MassItem.ChromatogramPeakType.VV) {
        return "VV";
    }
    return "Unknown";
}

// Returns the chromatogram type for the given chromatogram for retention time related fields which are saved with the mass spectrum
// These chromatograms can be TIC, UV, ELSD, CNLD, UVs at different wavelengths, etc.
function dbMassChromatogramType(aMassItem, aPlotNumber) {
    "use strict";
    var msInjection, msTrace,
        plot = aMassItem.plot(aPlotNumber);

    if (!plot.valid || plot.type !== MassItem.DataTypes.Chromatogram) { // 1
        return null;
    }
    if (plot.chromatogramType === MassItem.ChromatogramType.TIC) { // 1
        return "TIC";
    }
    if (plot.chromatogramType === MassItem.ChromatogramType.Trace) {    // 7
        if (plot.injectionIndex >= 0 && plot.traceIndex >= 0) {
            msInjection = aMassItem.info.injections.getItem(plot.injectionIndex);
            msTrace = msInjection.traces.getItem(plot.traceIndex);
            if (msTrace.name.indexOf("DAD A") === 0
                    || msTrace.name.indexOf("DAD B") === 0
                    || msTrace.name.indexOf("DAD C") === 0
                    || msTrace.name.indexOf("DAD D") === 0
                    || msTrace.name.indexOf("DAD1A") === 0
                    || msTrace.name.indexOf("DAD1B") === 0
                    || msTrace.name.indexOf("DAD1C") === 0
                    || msTrace.name.indexOf("DAD1G") === 0) {
                return "UV";
            }
            if (msTrace.name.indexOf("DAD") === 0) {
                return "UVT";
            }
            if (msTrace.name.indexOf("PDA") === 0) {
                return "UVT";
            }
            if (msTrace.name.indexOf("ELSD") === 0
                    || msTrace.name.indexOf("ELS1A")) {
                return "ELSD";
            }
            if (msTrace.name.indexOf("CNLD") === 0) {
                return "CNLD";
            }
        }
    }
    if (plot.chromatogramType === MassItem.ChromatogramType.UV) {       // 6
        return "UV";
    }
    if (plot.chromatogramType === MassItem.ChromatogramType.UVTAbs) {   // 5
        return "UVT";
    }
    return null;
}

// Returns the field prefix for the given chromatogram for retention time related fields which are saved with the mass spectrum.
// The database definition accommodates up to 8 traces (TIC, UVT = Total UV (or DAD or PDA), ELSD, CNLD + 4 UV traces as specific wavelengths)
// Possible fields: TIC, UVT, ELSD, CNLD, UV1, UV2, UV3, UV4. 
// Up to 4 UV traces are stored invo UV1, UV2, UV3, UV4. For all but UV (=TIC, UVT, ELSD, CNLD), only use first plot.
function dbRTFieldPrefix(aMassItem, aPlotNumber) {
    "use strict";
    var p, mct,
        uvbefore = 0,
        result = dbMassChromatogramType(aMassItem, aPlotNumber);

    for (p = 0; p < aPlotNumber; p++) {
        mct = dbMassChromatogramType(aMassItem, p);
        if (mct === result) {
            if (result === "UV") {
                uvbefore++;
            } else {
                result = null;
            }
        }
    }
    if (result === "UV") {
        uvbefore++;
        if (uvbefore <= 4) {
            result += uvbefore;
        } else {
            result = null;
        }
    }
    return result;
}

// -----------------------------------------------------------------------------------------------------------------------------------------------

// Returns the first MassFunctionInfo object for the given MassItem or null if not available.
// aPlot may be a plot index or a plot id or nothing.
function getMassFunctionInfo(aItem, aPlot) {
    "use strict";
    var plot, msinfo, injectioninfo, functionsinfo,
        massitem = new MassItem(aItem);
    plot = massitem.plot(aPlot);
    if (!plot) {
        return null;
    }
    //print("plot.valid = " + plot.valid);    
    if (!plot.valid) {
        return null;
    }
    msinfo = massitem.info;
    //print("msinfo.valid = " + msinfo.valid);    
    if (!msinfo.valid) {
        return null;
    }
    //print("injectionIndex = " + plot.injectionIndex);
    if (!(plot.injectionIndex >= 0 && plot.injectionIndex < msinfo.injections.count)) {
        return null;
    }
    injectioninfo = msinfo.injections.getItem(plot.injectionIndex);
    functionsinfo = injectioninfo.functions;
    //print("functionIndex = " + plot.functionIndex);
    if (!(plot.functionIndex >= 0 && plot.functionIndex < functionsinfo.count)) {
        return null;
    }
    return functionsinfo.getItem(plot.functionIndex);
}


function getMassInstrumentType(aItem, aPlot) {
    "use strict";
    var mfi = getMassFunctionInfo(aItem, aPlot);
    if (!mfi) {
        return "N/A";
    }
    if (mfi.analyzerType === MassItem.MSAnalyzer.Unknown) {
        return "Unknown";
    }
    if (mfi.analyzerType === MassItem.MSAnalyzer.Constant) {
        return "Constant";
    }
    if (mfi.analyzerType === MassItem.MSAnalyzer.FTMS) {
        return "FTMS";
    }
    if (mfi.analyzerType === MassItem.MSAnalyzer.Proportional) {
        return "Proportional";
    }
    if (mfi.analyzerType === MassItem.MSAnalyzer.TOF) {
        return "TOF";
    }
    if (mfi.analyzerType === MassItem.MSAnalyzer.Other) {
        return "Other";
    }
    return "-";
}

function getMassFunctionDescription(aItem, aPlot) {
    "use strict";
    var mfi = getMassFunctionInfo(aItem, aPlot);
    return mfi ? mfi.name : "N/A";
}

function getMassMsOrder(aItem, aPlot) {
    "use strict";
    var mfi = getMassFunctionInfo(aItem, aPlot);
    return mfi ? mfi.order : "N/A";
}

function getMassMSType(aItem, aPlot) {
    "use strict";
    var mfi = getMassFunctionInfo(aItem, aPlot),
        msTypes = {},
        result = "N/A";
    msTypes[MassItem.FunctionMSType.MS] = "MS";
    msTypes[MassItem.FunctionMSType.MSn] = "MSn";
    msTypes[MassItem.FunctionMSType.SIMMS] = "SIMMS";
    msTypes[MassItem.FunctionMSType.SIMMSn] =  "SIMMSn";
    msTypes[MassItem.FunctionMSType.ZoomMS] = "ZoomMS";
    msTypes[MassItem.FunctionMSType.ZoomMSn] = "ZoomMSn";

    if (mfi) {
        result = msTypes[mfi.msType];
        if (result === "undefined") {
            result = "-";
        }
    }
    return result;
}

function getMassMSMSType(aItem, aPlot) {
    "use strict";
    var mfi = getMassFunctionInfo(aItem, aPlot),
        msmsTypes = {},
        result = "N/A";
    msmsTypes[MassItem.FunctionMSMSType.None] = "None";
    msmsTypes[MassItem.FunctionMSMSType.NeutralLoss] = "NeutralLoss";
    msmsTypes[MassItem.FunctionMSMSType.Precursor] = "Precursor";
    msmsTypes[MassItem.FunctionMSMSType.Product] =  "Product";
    if (mfi) {
        result = msmsTypes[mfi.msmsType];
        if (result === "undefined") {
            result = "-";
        }
    }
    return result;
}

function getMassPolarity(aItem, aPlot) {
    "use strict";
    var mfi = getMassFunctionInfo(aItem, aPlot);
    if (!mfi) {
        return "N/A";
    }
    if (mfi.polarity === MassItem.FunctionPolarity.Mixed) {
        return "Mixed";
    }
    if (mfi.polarity === MassItem.FunctionPolarity.Positive) {
        return "Positive";
    }
    if (mfi.polarity === MassItem.FunctionPolarity.Negative) {
        return "Negative";
    }
    if (mfi.polarity === MassItem.FunctionPolarity.Unknown) {
        return "Unknown";
    }
    return "-";
}

// -----------------------------------------------------------------------------------------------------------------------------------------------
// Begin MassMetaDataHandler
// -----------------------------------------------------------------------------------------------------------------------------------------------

var MassMetaDataHandler = {};

MassMetaDataHandler.Field = function (aFieldName, aLabel, aDefaultValueFunction, aValidValues) {
    "use strict";
    this.fieldName = aFieldName;
    this.propertyName = "db." + aFieldName;
    this.settingsKey = "MassMetaData/" + aFieldName;
    this.label = aLabel;
    this.validValues = aValidValues;
    this.setValue = function (aItem, aPlotID, aValue) {
        aItem.setMetaData(this.propertyName + "." + aPlotID, aValue);
        new Settings().setValue(this.settingsKey, aValue);
    };
    this.getValue = function (aItem, aPlotID) {
        if (aItem.hasMetaData(this.propertyName + "." + aPlotID)) {
            return aItem.getMetaData(this.propertyName + "." + aPlotID);
        }
        return this.getDefaultValue(aItem, aPlotID);
    };
    this.getDefaultValue = function (aItem, aPlotID) {
        // 1. Use value from settings iff any
        // 2. Use value from specifed default value function iff any
        // 3. Return empty value
        return new Settings().value(this.settingsKey, aDefaultValueFunction ? aDefaultValueFunction(aItem, aPlotID) : "");
    };
};

MassMetaDataHandler.Control = function (aField, aItem, aPlotID) {
    "use strict";
    var i, val;
    this.field = aField;
    this.item = aItem;
    this.plotid = aPlotID;
    if (aField.validValues instanceof Array) {
        this.widget = new ComboBox();
        this.widget.addItem("");
        for (val = 0; val < aField.validValues.length; val++) {
            this.widget.addItem(aField.validValues[val]);
        }
        this.getWidgetData = function () {
            return this.widget.currentText;
        };
        this.setWidgetData = function (aText) {
            this.widget.currentIndex = 0;
            for (i = 0; i < this.field.validValues.length; i++) {
                if (this.field.validValues[i] === aText) {
                    this.widget.currentIndex = i + 1;
                }
            }
        };
    } else {
        this.widget = new LineEdit();
        this.getWidgetData = function () {
            return this.widget.text;
        };
        this.setWidgetData = function (aText) {
            this.widget.text = aText;
        };
    }
    this.clearWidgetData = function () {
        this.setWidgetData("");
    };
    this.resetWidgetData = function () {
        this.setWidgetData(this.field.getValue(this.item, this.plotid));
    };
    this.defaultWidgetData = function () {
        this.setWidgetData(this.field.getDefaultValue(this.item, this.plotid));
    };
    this.writeValue = function () {
        this.field.setValue(this.item, this.plotid, this.getWidgetData());
    };
};

MassMetaDataHandler.fields = [];
MassMetaDataHandler.fields[0]  = new MassMetaDataHandler.Field("Instrument Name", "Instrument Name");
MassMetaDataHandler.fields[1]  = new MassMetaDataHandler.Field("Instrument Type", "Instrument Type", getMassInstrumentType, ["Constant", "FTMS", "Proportional", "TOF", "Other", "Unknown"]);
MassMetaDataHandler.fields[2]  = new MassMetaDataHandler.Field("Function Description", "Function Description", getMassFunctionDescription);
MassMetaDataHandler.fields[3]  = new MassMetaDataHandler.Field("Ionization Method", "Ionization Method");
MassMetaDataHandler.fields[4]  = new MassMetaDataHandler.Field("Adduct", "Adduct");
MassMetaDataHandler.fields[5]  = new MassMetaDataHandler.Field("Charge State", "Charge State");
MassMetaDataHandler.fields[6]  = new MassMetaDataHandler.Field("MS Type", "MS Type");
MassMetaDataHandler.fields[7]  = new MassMetaDataHandler.Field("MS Order", "MS Order", getMassMsOrder);
MassMetaDataHandler.fields[8]  = new MassMetaDataHandler.Field("Precursor m/z", "Precursor m/z");
MassMetaDataHandler.fields[9]  = new MassMetaDataHandler.Field("Precursor Width", "Precursor Width");
MassMetaDataHandler.fields[10] = new MassMetaDataHandler.Field("Collision Energy", "Collision Energy");
MassMetaDataHandler.fields[11] = new MassMetaDataHandler.Field("Polarity", "Polarity", getMassPolarity, ["Positive", "Negative", "Mixed", "Unknown"]);

// Pops up a dialog allowing user to edit meta data values
MassMetaDataHandler.showDataDialog = function (aItem, aPlotID) {
    "use strict";
    var c, f, field, label, control, tbClear, tbReset, tbDefault,
        dialog = new Dialog(),
        gbMain = new GroupBox(),
        controls = [];

    dialog.title = "Data Fields";
    gbMain.title = "Data Fields";
    dialog.add(gbMain);

    for (f = 0; f < MassMetaDataHandler.fields.length; f++) {
        field = MassMetaDataHandler.fields[f];

        label = new Label();
        label.text = field.label + ": \t";

        control = new MassMetaDataHandler.Control(field, aItem, aPlotID);
        control.resetWidgetData();
        controls.push(control);

        tbClear = new ToolButton();
        tbClear.text = "Clear";
        tbClear.clicked.connect(control, control.clearWidgetData);

        tbReset = new ToolButton();
        tbReset.text = "Reset";
        tbReset.clicked.connect(control, control.resetWidgetData);

        tbDefault = new ToolButton();
        tbDefault.text = "Default";
        tbDefault.clicked.connect(control, control.defaultWidgetData);

        gbMain.add(label, control.widget, tbClear, tbReset, tbDefault);
    }

    if (!dialog.exec()) {
        return "Error: Canceled by user.";
    }
    for (c = 0; c < controls.length; c++) {
        controls[c].writeValue();
    }
    return "Ok";
};

// -----------------------------------------------------------------------------------------------------------------------------------------------
// End MassMetaDataHandler
// -----------------------------------------------------------------------------------------------------------------------------------------------

// DEPRECATED. Still here for databases created with Mnova < 7.1.1
// Valid values for aItem.name: {"Arrow", "Ellipse", "Image", "Layout Template", "Mass Spectrum", "Molecule", "NMR Spectrum", "OLE Object", "Polygon", "Rectangle", "Text"}
function getText(aItem) {
    "use strict";
    if (aItem.name === "NMR Spectrum") {
        return new NMRSpectrum(aItem).title;
    }
    if (aItem.name === "Mass Spectrum") {
        return new MassItem(aItem).plot().description;
    }
    if (aItem.name === "Text") {
        return new Text(aItem).plainText;
    }
    return "-";
}

function getSDFAsText(aItem, aSDFTag) {
    "use strict";
    var result = new Molecule(aItem).getSDFData(aSDFTag);
    return (result === undefined)  ?  "" : result;
}

// Converts a Nist SDF Mass Data ("MASS SPECTRAL PEAKS") to "Mass Data". It's to import process of Nist Databases.
function getMassDataFromNistSDFData(aText) {
    var l, lines, result;

    lines = aText.split("\n");
    result = "1 " + lines.length + "\n<null>\n";
    for (l = 0; l < lines.length; l++) {
        result += "{0} {1}\n".format(l + 1, lines[l]);
    }
    return result;
}

// Converts a "Mass Data" to Nist SDF Mass Data ("MASS SPECTRAL PEAKS") 
function getNistSDFDataFromMassData(aText) {
    var l,lines,line,result = "";

    lines = aText.split(/\r?\n/);
    for (l = 2; l < lines.length; l++) {
        line = lines[l].trim().split(/\s+/);
        if (line.length > 2){
            result += "{0} {1}\n".format(line[1], line[2]);
        }
    }
    return result;
}

function getMassDataFromNistSDF(aItem) {
    "use strict";
    var sdftext = new Molecule(aItem).getSDFData("MASS SPECTRAL PEAKS");
    if (sdftext === undefined) {
        return "0 0";
    }
    return getMassDataFromNistSDFData(sdftext);
}

function dbMergePageNotes(aPageNumber, aNotes) {
    "use strict";
    //print("mergePageNotes(" + aPageNumber + ",'" + aNotes + "')");
    var page,
        doc = new DocumentWindow(Application.mainWindow.activeWindow());
    if (aPageNumber < 0 || aPageNumber >= doc.pageCount()) {
        return;
    }
    page = doc.page(aPageNumber);
    if (page.notes.indexOf(aNotes) < 0) {
        if (page.notes.length > 0) {
            page.notes += "\n";
        }
        page.notes += aNotes;
    }
}

// DEPRECATED. Still here for databases created with Mnova < 7.1.1
function getPageNotes(aItemID) {
    "use strict";
    var p, page, i,
        doc = new DocumentWindow(Application.mainWindow.activeWindow());
    for (p = 0; p < doc.pageCount(); p++) {
        page = doc.page(p);
        for (i = 0; i < page.itemCount(); i++) {
            if (page.item(i).uuid === aItemID) {
                return page.notes;
            }
        }
    }
    return "";
}

// DEPRECATED. Still here for databases created with Mnova < 7.1.1
function mergePageNotes(aItemID, aNotes) {
    "use strict";
    //print("mergePageNotes(" + aItemID + ",'" + aNotes + "')");
    var p, page, i,
        doc = new DocumentWindow(Application.mainWindow.activeWindow());
    for (p = 0; p < doc.pageCount(); p++) {
        page = doc.page(p);
        for (i = 0; i < page.itemCount(); i++) {
            if (page.item(i).uuid === aItemID) {
                dbMergePageNotes(p, aNotes);
                return;
            }
        }
    }
}

// Finds a text item by type and subtype and returns its contents
function getNamedTextField(aType, aSubtype) {
    "use strict";
    var i, item,
        dw = new DocumentWindow(mainWindow.activeWindow());
    for (i = 0; i < dw.itemCount(); i++) {
        item = dw.item(i);
        if (item.name === "Text" && item.type === aType && item.subtype === aSubtype) {
            item.update();
            return new Text(item).plainText;
        }
    }
    return "-";
}

// Returns true iff aX is between aY and aZ
function isBetween(aX, aY, aZ) {
    "use strict";
    return ((aY <= aX && aX <= aZ) || (aZ <= aX && aX <= aY));
}

// Returns true iff [aA,aB] is completetely included in [aC,aD]
function isInside(aA, aB, aC, aD) {
    "use strict";
    return isBetween(aA, aC, aD) && isBetween(aB, aC, aD);
}

// -----------------------------------------------------------------------------------------------------------------------------------------------

// Returns the metadata for the given mass spectrum as an associative array [Field Name] => [value] 
function dbGetMassSpecMetaData(aItem, aPlotID) {
    "use strict";
    var f, field,
        fields = {},
        settings = new Settings("Database");
    if (!aItem.hasMetaData("db.NoDialog") && settings.value("Show Data Dialogs", true)) {
        MassMetaDataHandler.showDataDialog(aItem, aPlotID);
    }
    for (f = 0; f < MassMetaDataHandler.fields.length; f++) {
        field = MassMetaDataHandler.fields[f];
        fields[field.fieldName] = field.getValue(aItem, aPlotID);
    }
    return fields;
}

// -----------------------------------------------------------------------------------------------------------------------------------------------

// Add some db fields common to all items to the given db item.
function dbAddCommonFields(aDBItem, aPageItem) {
    "use strict";
    var pageItem;
    //print(aPageItem.constructor.name);
    if (aPageItem) {                                            // mgo 2014-04-02: Bug #7172 aPageItem may be null (for page notes)
        pageItem = new PageItem(aPageItem);                     // mgo 2014-04-02: Bug #7166 (Molecule.name hides PageItem.name) is fixed but better be safe
        aDBItem.addField("Uuid", pageItem.uuid);
        aDBItem.addField("LinkID", pageItem.linkId);
        aDBItem.addField("CustomID", pageItem.getMetaData("CustomID"));
        aDBItem.addField("ParentID", pageItem.parentUuid);
        aDBItem.addField("Preview", DBPlugin.getPreview(pageItem));
        aDBItem.addField("Page", mainWindow.activeDocument.pageIndex(pageItem.page) + 1);     // 0 for invalid page
        aDBItem.addField("PageID", pageItem.page ? pageItem.page.uuid : "");
        aDBItem.addField("PageHeight", pageItem.page ? pageItem.page.height : "0");
        aDBItem.addField("PageWidth", pageItem.page ? pageItem.page.width : "0");
    }
    aDBItem.addField("Host", DBPlugin.client.name);
    aDBItem.addField("Document", mainWindow.activeWindow().name);
    aDBItem.addField("DocumentID", mainWindow.activeWindow().uuid);
    aDBItem.addField("User", DBPlugin.client.user);
    aDBItem.addField("Created", "NOW");
    aDBItem.addField("Modified", "NOW");
    aDBItem.addField("Mnova Version", Application.version.full);
}

// Add custom fields from the given db definition to the given db item.
// We need the unused 'item' parameter in tryEval.
/*jslint unparam: true */
function dbAddCustomFields(aDBItem, aPageItem, aDB) {
    "use strict";
    var f, contentDef,
        fields = aDB.fields(aDBItem.type);

    function tryEval(aCode, item) {
        // we need item to de defined here for the scriptlets in db definition
        try {
            return eval(aCode);
        } catch (e) {
            return "Error: " + e.name + " - " + e.message;
        }
    }

    for (f = 0; f < fields.length; ++f) {
        if (!aDBItem.hasField(fields[f])) {
            contentDef = Str.trimStr(aDB.contentDef(aDBItem.type, fields[f]));
            //print(contentDef);
            if (contentDef.indexOf("=") === 0) {
                aDBItem.addField(fields[f], tryEval(contentDef.substr(1), aPageItem));
            }
        }
    }
}
/*jslint unparam: false */

function dbMoleculeToDBItem(aDB, aMolecule) {
    "use strict";
    var dbItem = new DBItem("Molecule");
    dbItem.addField("Type", "Molecule");
    dbItem.addField("Name", aMolecule.molName);
    dbItem.addField("Label", aMolecule.label);
    dbItem.addField("Description", aMolecule.description);
    dbItem.addField("Aliases", aMolecule.aliases.join("\n"));
    dbItem.addField("Atoms", aMolecule.atomCount);
    dbItem.addField("Bonds", aMolecule.bondCount);
    dbItem.addField("MoleculeId", aMolecule.moleculeId);
    dbItem.addField("CHEMICAL STRUCTURE", aMolecule.getMolfile());
    dbItem.addField("Molecular Formula", aMolecule.molecularFormula());
    dbItem.addField("Average Mass", aMolecule.averageMass());
    dbItem.addField("Monoisotopic Mass", aMolecule.monoisotopicMass());
    dbItem.addField("DbaId", aMolecule.getParam("DBAID"));
    dbItem.addField("Assignments 1H", dbGetAssignments(aMolecule, "1H"));
    dbItem.addField("Assignments 13C", dbGetAssignments(aMolecule, "13C"));
    dbAddCommonFields(dbItem, aMolecule);
    dbAddCustomFields(dbItem, aMolecule, aDB);
    return dbItem;
}

function dbNMRSpectrumToDBItem(aDB, aSpectrum) {
    "use strict";
    var i,
        paramNames = aSpectrum.paramNames(),
        dbItem = new DBItem("NMR Spectrum");
    dbItem.addField("Type", "NMR Spectrum");
    dbItem.addField("Description", aSpectrum.title);
    for (i = 0; i < paramNames.length; i++) {
        if (aDB.hasField("NMR Spectrum", paramNames[i])) {
            dbItem.addField(paramNames[i], aSpectrum.getParam(paramNames[i]));
        }
        if (aDB.hasField("NMR Spectrum", paramNames[i] + "[1]")) {
            dbItem.addField(paramNames[i] + "[1]", aSpectrum.getParam(paramNames[i] + "[1]"));
        }
        if (aDB.hasField("NMR Spectrum", paramNames[i] + "[2]")) {
            dbItem.addField(paramNames[i] + "[2]", aSpectrum.getParam(paramNames[i] + "[2]"));
        }
    }
    dbItem.addField("Peaks1D", getNMRPeaks(aSpectrum, 1));
    dbItem.addField("Multiplets1D", getNMRMultiplets(aSpectrum, 1));
    dbItem.addField("Integrals1D", getIntegrals(aSpectrum, 1));
    dbItem.addField("Peaks2D", getNMRPeaks(aSpectrum, 2));
    dbItem.addField("Integrals2D", getIntegrals(aSpectrum, 2));
    dbItem.addField("Spectrum Title", aSpectrum.title);
    dbItem.addField("Spectrum Comment", aSpectrum.comment);
    dbItem.addField("Experiment Type", aSpectrum.experimentType);
    dbItem.addField("Original Format", aSpectrum.originalFormat);
    dbItem.addField("Solvent", aSpectrum.solvent);
    dbAddCommonFields(dbItem, aSpectrum);
    dbAddCustomFields(dbItem, aSpectrum, aDB);
    return dbItem;
}

function dbMassChromatogramToDBItem(aMassItem, aPlotNumber) {
    "use strict";

    var dbItem = new DBItem("Mass Chromatogram"),
        plot = aMassItem.plot(aPlotNumber),
        mfi = getMassFunctionInfo(aMassItem, aPlotNumber);

    dbItem.addField("Chromatogram Type", dbMassChromatogramType(aMassItem, aPlotNumber));
    dbItem.addField("Peaks", aMassItem.dbStore(plot.id));
    if (mfi) {
        dbItem.addField("Function Description", mfi.name);
        dbItem.addField("Instrument Type", getMassInstrumentType(aMassItem, plot.id));
        dbItem.addField("MS Order", mfi.order);
        dbItem.addField("MS Type", getMassMSType(aMassItem, aPlotNumber));
        dbItem.addField("MS/MS Type", getMassMSMSType(aMassItem, aPlotNumber));
        dbItem.addField("Polarity", getMassPolarity(aMassItem, aPlotNumber));
    }
    return dbItem;
}

function dbMassSpectrumToDBItem(aDB, aMassItem, aPlotNumber) {
    "use strict";

    function findBestChromPeak(aMassSpectrum, aMassChromatogram) {
        // Find corresponding chromatogram peak (= tallest peak within mass spectrum ranges)
        if (aMassChromatogram.peaks === undefined) {
            return undefined;
        }
        var bestChromPeak, p, currPeak, ft, foreRange;
        for (p = 0; p < aMassChromatogram.peaks.length; p++) {
            currPeak = aMassChromatogram.peaks[p];
            //print("  currPeak: " + currPeak.center.x);
            for (ft = 0; ft < aMassSpectrum.times.foreTimes.length; ft++) {
                foreRange = aMassSpectrum.times.foreTimes[ft];
                //print("   ft=" + ft + " [" + foreRange.from + ".." + foreRange.to + "]");
                if (isBetween(currPeak.center.x, foreRange.from, foreRange.to) || isInside(foreRange.from, foreRange.to, currPeak.start.x, currPeak.end.x)) {
                    if (bestChromPeak === undefined || currPeak.height > bestChromPeak.height) {
                        //print("    IsBetween");
                        bestChromPeak = currPeak;
                    }
                }
            }
        }
        return bestChromPeak;
    }

    var c, chrom, prefix, bestChromPeak, peak, chromPeaksTotalHeight, chromPeaksTotalArea,
        dbItem = new DBItem("Mass Spectrum"),
        plot = aMassItem.plot(aPlotNumber);

    dbItem.addField("Mass Data", aMassItem.dbStore(plot.id));

    // Iterate through chromatogram plots for RT fields
    for (c = 0; c < aMassItem.plotsCount; c++) {
        chrom = aMassItem.plot(c);
        prefix = dbRTFieldPrefix(aMassItem, c);
        if (chrom.valid && chrom.visible && chrom.type === MassItem.DataTypes.Chromatogram && aDB.hasField("Mass Spectrum", prefix + "_RT")) {
            //print(" Chromatogram " + c);
            // Find corresponding chromatogram peak (= tallest peak within mass spectrum ranges)
            bestChromPeak = findBestChromPeak(plot, chrom);
            if (bestChromPeak !== undefined) {
                // Calculate total Height and Area of all chromatogram peaks
                chromPeaksTotalHeight = 0.0;
                chromPeaksTotalArea = 0.0;
                for (peak = 0; peak < chrom.peaks.length; peak++) {
                    chromPeaksTotalHeight += chrom.peaks[peak].height;
                    chromPeaksTotalArea += chrom.peaks[peak].area;
                }
                // Add fields for corresponding chromatogram peak
                dbItem.addField(prefix + "_RT", bestChromPeak.center.x);
                dbItem.addField(prefix + "_Scan", bestChromPeak.scan);
                dbItem.addField(prefix + "_Type", massChromatogramPeakType(bestChromPeak.type));
                dbItem.addField(prefix + "_Height", bestChromPeak.height);
                dbItem.addField(prefix + "_Area", bestChromPeak.area);
                if (chromPeaksTotalHeight > 0.0) {
                    dbItem.addField(prefix + "_HeightPercent", bestChromPeak.height * 100.0 / chromPeaksTotalHeight);
                }
                if (chromPeaksTotalArea > 0.0) {
                    dbItem.addField(prefix + "_AreaPercent", bestChromPeak.area * 100.0 / chromPeaksTotalArea);
                }
                dbItem.addField(prefix + "_StartTime", bestChromPeak.start.x);
                dbItem.addField(prefix + "_EndTime", bestChromPeak.end.x);
            }
        }
    }
    return dbItem;
}

function dbMassItemSetActivePlot(aPageItem, aPlotId) {
    massItem = new MassItem(aPageItem);
    massItem.setActivePlot(aPlotId);
}

function dbMassItemToDBItems(aDB, aMassItem) {
    "use strict";

    var p, plot, dbItem,
        dbItems = [];

    // Iterate through plots, building one dbItem for each plot
    for (p = 0; p < aMassItem.plotsCount; p++) {
        plot = aMassItem.plot(p);
        if (plot.valid && plot.visible) {
            dbItem = null;
            if (plot.type === MassItem.DataTypes.Chromatogram && aDB.hasItemType("Mass Chromatogram")) {
                dbItem = dbMassChromatogramToDBItem(aMassItem, p);
            } else if (plot.type === MassItem.DataTypes.Spectrum && aDB.hasItemType("Mass Spectrum")) {
                dbItem = dbMassSpectrumToDBItem(aDB, aMassItem, p);
            } else if (plot.type === MassItem.DataTypes.UVSpectrum) {
                print("UVSpectrum " + p);
            } else {
                print("Unknown mass plot type '{0}' for plot {1}".format(plot.type, p));
            }
            if (dbItem) {
                dbItem.addField("Type", dbItem.type);                  // "Mass Spectrum", "Mass Chromatogram";
                dbItem.addField("Description", plot.description);
                dbItem.addField("Dataset FileName", aMassItem.datasetFileName);
                dbItem.addField("PlotID", plot.id);
                dbItem.addField("PlotNumber", p + 1);
                dbAddCommonFields(dbItem, aMassItem);
                dbAddCustomFields(dbItem, aMassItem, aDB);
                dbItems.push(dbItem);
            }
        }
    }
    return dbItems;
}

function dbElViSSpectrumToDBItem(aDB, aSpectrum) {
    "use strict";
    var dbItem = new DBItem("ElViS Spectrum");
    dbItem.addField("Type", "ElViS Spectrum");
    dbItem.addField("Original Format", aSpectrum.originalFormat);
    dbItem.addField("Spectrum Title", aSpectrum.title);
    dbItem.addField("Spectrum Comment", aSpectrum.comment);
    dbItem.addField("Peaks", dbGetElViSPeaks(aSpectrum));
    dbItem.addField("Points", dbGetElViSPoints(aSpectrum));
    dbItem.addField("NumPeaks", aSpectrum.peaks() ? aSpectrum.peaks().length : 0);
    dbItem.addField("NumPoints", aSpectrum.count());
    dbAddCommonFields(dbItem, aSpectrum);
    dbAddCustomFields(dbItem, aSpectrum, aDB);
    return dbItem;
}

// Convert a Page Item to an array of DB items.
function dbPageItemToDBItems(aDB, aPageItem) {
    "use strict";
    var dbItem, massDBItems, i,
        dbItems = [];

    print("PageItem: UUID={0} Name={1} Page='{2}'".format(aPageItem.uuid, aPageItem.name, mainWindow.activeDocument.pageIndex(aPageItem.page) + 1));
    if (aPageItem.name === "Molecule") {
        dbItems.push(dbMoleculeToDBItem(aDB, new Molecule(aPageItem)));
    } else if (aPageItem.name === "NMR Spectrum" && aDB.hasItemType("NMR Spectrum")) {
        dbItems.push(dbNMRSpectrumToDBItem(aDB, new NMRSpectrum(aPageItem)));
    } else if (aPageItem.name === "Mass Spectrum") {
        massDBItems = dbMassItemToDBItems(aDB, new MassItem(aPageItem));
        for (i = 0; i < massDBItems.length; i++) {
            dbItems.push(massDBItems[i]);
        }
    } else if (aPageItem.name === "ElViS Spectrum" && aDB.hasItemType("ElViS Spectrum")) {
        dbItem = dbElViSSpectrumToDBItem(aDB, new ElViSSpectrum(aPageItem));
        dbItems.push(dbItem);
    } else if (aPageItem.name === "Text" && aDB.hasItemType("Mnova")) {
        dbItem = new DBItem("Mnova");
        dbItem.addField("Type", aPageItem.name);
        dbItem.addField("Description", new Text(aPageItem).plainText);
        dbAddCommonFields(dbItem, aPageItem);
        dbAddCustomFields(dbItem, aPageItem, aDB);
        dbItems.push(dbItem);
    } else if (aDB.hasItemType("Mnova")) {
        // Anything else: images, ole, ... 
        dbItem = new DBItem("Mnova");
        dbItem.addField("Type", aPageItem.name);
        dbAddCommonFields(dbItem, aPageItem);
        dbAddCustomFields(dbItem, aPageItem, aDB);
        dbItems.push(dbItem);
    } else {
        print("Unhandled PageItem: UUID={0} Name={1} Page='{2}'".format(aPageItem.uuid, aPageItem.name, mainWindow.activeDocument.pageIndex(aPageItem.page) + 1));
        print(aDB.itemTypes());
    }
    //print(dbItems);
    return dbItems;
}

// Convert the given page items to an array of db items:
function dbPageItemsToDBItems(aDB, aPageItems) {
    "use strict";
    var pi, dbItems, i,
        result = [];
    for (pi = 0; pi < aPageItems.length; pi++) {
        dbItems = dbPageItemToDBItems(aDB, aPageItems[pi]);
        for (i = 0; i < dbItems.length; i++) {
            result.push(dbItems[i]);
        }
    }
    return result;
}

// This function is called when saving to database. It returns an array of DBItems built from the current document.
// Parameters: aDBClient - DBClient object which is connected to the server we want to save to
//             aDB - DBDefinition object for the database where items will be saved to
function dbMakeDBItems(aDBClient, aDB, aShowErrors, aInteractive) {
    "use strict";
    var i, p, page, pageItem, dbItem, dbItems, metaData, fieldName, saveid,
        dw = mainWindow.activeWindow(),
        pageItems = [];

    if (!dw) {
        return [];
    }

    // Collect page items from current document:
    for (i = 0; i < dw.itemCount(); i++) {
        pageItems.push(dw.item(i));
    }

    // Use molecules table only if there are no molecules in pages
    if (pageItems.filter(function (i) { return i.name === "Molecule"; }).length === 0) {
        for (i = 0; i < dw.getMolTableItemCount(); i++) {
            pageItems.push(dw.getMolTableItem(i));
        }
    }
    print("{0} Page items.".format(pageItems.length));

    // Build db items from page items:
    mainWindow.setBusy("Preparing...");
    dbItems = dbPageItemsToDBItems(aDB, pageItems);
    mainWindow.endBusy();

    // Add page notes
    if (aDB.hasItemType("Page Note")) {
        for (p = 0; p < mainWindow.activeDocument.pageCount(); p++) {
            page = mainWindow.activeDocument.page(p);
            if (page.notes) {
                dbItem = new DBItem("Page Note");
                dbItem.addField("Type", "Page Note");
                dbItem.addField("Description", page.notes);
                dbItem.addField("Text", page.notes);
                dbItem.addField("Page", p + 1);
                dbItem.addField("PageID", page.uuid);
                dbItem.addField("Uuid", page.uuid);
                dbAddCommonFields(dbItem, null);
                dbItems.push(dbItem);
            }
        }
    }
    print("{0} DB items.".format(dbItems.length));


    // Show the item selection dialog
    if (dbItems.length > 0 && aInteractive) {
        dbItems = DBPlugin.selectDBItems(aDB, dbItems);
    }

    // Call Pre-Save hook if it exists
    if (typeof dbPreSaveHook === 'function') {
        dbPreSaveHook(aDBClient, aDB, 0, dbItems, aShowErrors, aInteractive);
    }

    // Add custom field contents
    for (i = 0; i < dbItems.length; i++) {
        pageItem = dbGetItem(dbItems[i].field("Uuid"));
        if (pageItem) {
            // Show metadata dialogs for MassItem plots
            if (dbItems[i].type === "Mass Spectrum") {
                metaData = dbGetMassSpecMetaData(pageItem, dbItems[i].field("PlotID"));
                for (fieldName in metaData) {
                    if (metaData.hasOwnProperty(fieldName)) {
                        dbItems[i].setField(fieldName, metaData[fieldName]);
                    }
                }
            }
        }
    }

    //print("dbMakeDBItems script [End]");
    return dbItems;
}

// ---------------------------------------------------------------------------------------------------------------------

// Special paste function for NIST17 database distributed by Mestrelab - creates molecule and MS spectrum.
function dbPasteNIST17(aDBItem) {
    "use strict";
    var mol_id, mol_item, msdata, msFileName, page,
        doc = mainWindow.activeDocument,
        sdf = aDBItem.field("MNPASTE");

    if (!doc) {
        // #16445: No document open
        doc = new Document();
        mainWindow.addDocument(doc);
    }
    page = doc.curPage();
    if (!page || !page.uuid || page.itemCount() > 0) {
        page = doc.newPage();
    }
    //MessageBox.information("SDF data:\n" + sdf);
    mol_id = molecule.importMolfile(sdf);
    //MessageBox.information("mol_id:\n" + mol_id);
    mol_item = new Molecule(dbGetItem(mol_id));
    msdata = mol_item.getSDFData("MASS SPECTRAL PEAKS");
    //MessageBox.information("MS data:\n" + msdata);
    msFileName =  Dir.temp() + "/mnpaste_" + mol_item.uuid + ".csv";
    dbCreateTextFile(msFileName, msdata);
    if (serialization.open(msFileName, "MS CSV File (*.csv *.txt)")) {
        File.remove(msFileName);
    } else {
        MessageBox.warning(qsTr("Error opening MS Spectrum from " + msFileName));
    }
}


function dbPasteNIST20(aDBItem) {
    "use strict";
    var msdata, msFileName, page,
        doc = mainWindow.activeDocument;

    if (!doc) {
        // #16445: No document open
        doc = new Document();
        mainWindow.addDocument(doc);
    }
    page = doc.curPage();
    if (!page || !page.uuid || page.itemCount() > 0) {
        page = doc.newPage();
    }

    if (aDBItem.type === "Molecule") {
        molecule.importMolfile(aDBItem.field("MNPASTE"));
        //mol_id = molecule.importMolfile(aDBItem.field("MNPASTE"));
        //MessageBox.information("mol_id:\n" + mol_id);
        //mol_item = new Molecule(dbGetItem(mol_id));
    } else if (aDBItem.type === "Mass Spectrum") {
        msdata = aDBItem.field("MASS SPECTRAL PEAKS");
        //MessageBox.information("MS data:\n" + msdata);
        msFileName =  Dir.temp() + "/mnpaste-" +  Application.createUuid().replace(/[\{\}]/g, "") + ".csv";
        dbCreateTextFile(msFileName, msdata);
        if (serialization.open(msFileName, "MS CSV File (*.csv *.txt)")) {
            File.remove(msFileName);
        } else {
            MessageBox.warning(qsTr("Error opening MS Spectrum from " + msFileName));
        }
    } else {
        MessageBox.warning(qsTr("Unknown item type: " + aDBItem.type));
    }
}

// This function is called for each db item before pasting from MnDB to Mnova. 
// It must return true for the paste operation to proceed.
function dbPrePaste(aItemUrl, aDB, aItem) {
    "use strict";
    // Call custom function if it exists
    if (typeof dbPrePasteHook === 'function') {
        return dbPrePasteHook(aItemUrl, aDB, aItem);
    }
    // Mestrelab reference databases
    if (aDB.name.startsWith("NIST17") && aItem.type === "Molecule" && aItem.hasField("MNPASTE")) {
        dbPasteNIST17(aItem);
        return false;
    }
    if (aDB.name.startsWith("Nist20")) {
        dbPasteNIST20(aItem);
        return false;
    }
    return true;
}

// This function is called once after each successful pasting operation from MnDB to Mnova.
function dbPostPaste(aUuids) {
    "use strict";
    // Call custom function if it exists
    if (typeof dbPostPasteHook === 'function') {
        return dbPostPasteHook(aUuids);
    }
}

// This function is called before the SpectralWidget paint a new CanvasItem. 
function dbPreSpectralWidget(aPageItem) {
    if (aPageItem.name === "NMR Spectrum") {
        nmrItem = NMRSpectrum(aPageItem);
        nmrItem.setProperty("title.format", "{meta,\"mndb.title\",1}");
        nmrItem.setProperty("title.visible", true);
    }
    if (aPageItem.name === "Mass Spectrum") {
        massItem =  MassItem(aPageItem);
        for (c = 0; c < massItem.plotsCount; c++) {
            massItem.plot(c).properties.title.format = "{meta,\"mndb.title\",1}";
            massItem.plot(c).properties.title.stacked = true;
            massItem.plot(c).properties.title.position = "Inside";
            // print("plot " + c + " title formated.");
        }
    }
}

// ---------------------------------------------------------------------------------------------------------------------

// Convert the given DBItem to a PageItem:
function dbItemToPageItem(aDBItem) {
    "use strict";

    switch(aDBItem.type) {
        case "Molecule": return dbPrivate.dbItemMoleculeToPageItem(aDBItem);
        case "Mass Spectrum": return dbPrivate.dbItemMassSpectrumToPageItem(aDBItem);
        case "NMR Spectrum": return dbPrivate.dbItemNMRSpectrumToPageItem(aDBItem);
        default: return null;
      }
}

// Convert the given DBQuery (in XML format) to a PageItem:
function dbQueryToPageItem(aXml) {
    "use strict";
   
    // Obtain a DbQuery Json Object  from xml
    var tempFile = Dir.temp() + "/dbQueryToPageItem-" +  Application.createUuid().replace(/[\{\}]/g, "") + ".xml";
    dbCreateTextFile(tempFile,aXml);   
    var dbQuery = XmlToJson.parseXmlFile(tempFile);
    File.remove(tempFile);

    // Compose a CanvasItem from DbQuery Json Object
    switch(dbQuery.Type) {
        case "Molecular Structure Query": return dbPrivate.dbQueryMoleculeToPageItem(dbQuery);
        case "Mass Data Query": return dbPrivate.dbQueryMassToPageItem(dbQuery);
        case "1D Peaks Query": return dbPrivate.dbQueryNmrPeaksToPageItem(dbQuery);
        case "Multiplets Query": return dbPrivate.dbQueryNmrPeaksToPageItem(dbQuery);
        case "Spectrum Query":  return dbPrivate.dbQueryNmrSpectrumToPageItem(dbQuery);
        default: return null;
      }
}



// ---------------------------------------------------------------------------------------------------------------------
// dbPrivate contains helper functions which are not used anywhere outside this script.
// ---------------------------------------------------------------------------------------------------------------------

var dbPrivate = {};

// Convert the given DBItem Molecule to a PageItem:
dbPrivate.dbItemMoleculeToPageItem = function(aDBItem) {
    "use strict";
    var molFile = aDBItem.field("CHEMICAL STRUCTURE");
    if (molFile){
        return DBPlugin.pageItemFromMolFile(molFile);
    }else{
        return null;
    }
};

// Convert the given DBItem Mass Spectrum to a PageItem:
dbPrivate.dbItemMassSpectrumToPageItem = function(aDBItem) {
    "use strict";
    // Convert DbItem to Mass raw data
    var  massData = getNistSDFDataFromMassData(aDBItem.field("Mass Data"));

    // Convert raw data to CanvasItem
    return dbPrivate.rawdataToCanvasitem(massData,".csv");
};

// Convert the given DBItem NMR Spectrum to a PageItem:
dbPrivate.dbItemNMRSpectrumToPageItem  = function(aDBItem) {
    "use strict";
    // Compose NMR Xml
    if (!aDBItem.field("Peaks1D")){
        return null;
    }
    var spectrumNucleus, spectrumFrom, spectrumTo, SpectralSize, SpectrometerFrequency, LineWidth;
    spectrumNucleus = aDBItem.field("Nucleus");
    if (aDBItem.field("Lowest Frequency")) spectrumFrom = parseInt(aDBItem.field("Lowest Frequency"),10)/1000;
	if (aDBItem.field("Spectral Width")) spectrumTo = parseInt(aDBItem.field("Spectral Width"),10)/1000;
	if (aDBItem.field("Spectral Size")) SpectralSize = parseInt(aDBItem.field("Spectral Size"),10);
	if (aDBItem.field("Spectrometer Frequency")) SpectrometerFrequency = aDBItem.field("Spectrometer Frequency");
    if (aDBItem.field("Pulse Width")) LineWidth = aDBItem.field("Pulse Width");

    var peakList = [];      // array of peaks {pos,width,intensity}
    var l,lines = aDBItem.field("Peaks1D").split(/\r?\n/);
    for (l = 1; l < lines.length; l++) {
        line = lines[l].trim().split(/\s+/);
        if (line.length > 3){
            peakList.push([parseFloat(line[1]),line[2],line[3]]);
        }
    }
    xml = dbPrivate.xmlNmr1DSpectrum(spectrumNucleus, spectrumFrom, spectrumTo, SpectralSize, SpectrometerFrequency, LineWidth, peakList);
    if (!xml){
        return null;
    }

    // Convert raw data to CanvasItem
    return dbPrivate.rawdataToCanvasitem(xml,".xml");
};

// Convert the given DBQuery Molecule to a PageItem:
dbPrivate.dbQueryMoleculeToPageItem = function(aDbQuery) {
    "use strict";
    var molFile = aDbQuery.Molecule;
    return DBPlugin.pageItemFromMolFile(molFile);
};

// Convert the given DBQuery Mass to a PageItem:
dbPrivate.dbQueryMassToPageItem = function(aDbQuery) {
    "use strict";
    // Compose Nist SDF Mass Raw Data from Mass Query
    var i,massData = "";
    for (i = 0; i < aDbQuery.Clauses.Count; i++) {
        massData += "{0} {1}\n".format(aDbQuery.Clauses.Clause[i].MZ, aDbQuery.Clauses.Clause[i].Intensity);
    }

     // Convert raw data to CanvasItem
    return dbPrivate.rawdataToCanvasitem(massData,".csv");
};

// Convert the given DBQuery Nmr (Peaks or Multiplets) to a PageItem:
dbPrivate.dbQueryNmrPeaksToPageItem = function(aDbQuery) {
    "use strict";
    
    // Obtain neccesary data to compose Nmr Xml
    var spectrumNucleus,spectrumFrom,spectrumTo,SpectralSize,SpectrometerFrequency,LineWidth;
    spectrumNucleus = aDbQuery.Nucleus1;
    if (aDbQuery.Spectrum.hasOwnProperty('FromX')) spectrumFrom = parseInt(aDbQuery.Spectrum.FromX,10)/1000;
	if (aDbQuery.Spectrum.hasOwnProperty("ToX")) spectrumTo = parseInt(aDbQuery.Spectrum.ToX,10)/1000;
	if (aDbQuery.Spectrum.hasOwnProperty("Size")) SpectralSize = parseInt(aDbQuery.Spectrum.Size,10);
	if (aDbQuery.Spectrum.hasOwnProperty("Frequency")) SpectrometerFrequency = aDbQuery.Spectrum.Frequency;
    if (aDbQuery.Spectrum.hasOwnProperty("LineWidth")) LineWidth = aDbQuery.Spectrum.LineWidth;

    var i,pos,witdh,intensity,peakList = [];
    for (i = 0; i < aDbQuery.Clauses.Count; i++) {
        if (aDbQuery.Clauses.Clause[i].hasOwnProperty('Peaks')){
            var peaks = aDbQuery.Clauses.Clause[i].Peaks;
            if (peaks.Count == 1){
                pos = parseFloat(peaks.Peak.PosX);
                witdh = parseFloat(peaks.Peak.WidthX);
                intensity = parseFloat(peaks.Peak.Intensity);
                peakList.push([pos, witdh, intensity]);
            }else{
                for (p = 0; p < peaks.Count; p++) {
                    pos = parseFloat(peaks.Peak[p].PosX);
                    witdh = parseFloat(peaks.Peak[p].WidthX);
                    intensity = parseFloat(peaks.Peak[p].Intensity);
                    peakList.push([pos, witdh, intensity]);
                }
            }
        }
    }
    if (peakList.length == 0){
        return null;
    }

    // Compose NMR Xml
    xml = dbPrivate.xmlNmr1DSpectrum(spectrumNucleus,spectrumFrom,spectrumTo,SpectralSize,SpectrometerFrequency,LineWidth,peakList);
    if (!xml){
        return null;
    }

    // Convert raw data to CanvasItem
    return dbPrivate.rawdataToCanvasitem(xml,".xml");
};

// Convert the given DBQuery Nmr Spectrum to a PageItem:
dbPrivate.dbQueryNmrSpectrumToPageItem = function(aDbQuery) {
    "use strict";

    // Obtain neccesary data to compose Nmr
    aDbQuery = {'JsonData': JSON.parse(aDbQuery.JsonData), 'Spectrum': aDbQuery.Spectrum};

    var spectrumNucleus,spectrumFrom,spectrumTo,SpectralSize,SpectrometerFrequency,LineWidth;
    var points = [];

    if (aDbQuery.Spectrum.hasOwnProperty('Nucleus1')) spectrumNucleus = aDbQuery.Spectrum.Nucleus1;
    if (aDbQuery.JsonData.hasOwnProperty('limits')){
        if (aDbQuery.JsonData.limits.hasOwnProperty('fromX')) spectrumFrom = parseInt(aDbQuery.JsonData.limits.fromX,10);
        if (aDbQuery.JsonData.limits.hasOwnProperty("toX")) spectrumTo = parseInt(aDbQuery.JsonData.limits.toX,10);
    }
    if (aDbQuery.Spectrum.hasOwnProperty("Size")) SpectralSize = parseInt(aDbQuery.Spectrum.Size,10);
	if (aDbQuery.Spectrum.hasOwnProperty("Frequency")) SpectrometerFrequency = aDbQuery.Spectrum.Frequency;
    if (aDbQuery.Spectrum.hasOwnProperty("LineWidth")) LineWidth = aDbQuery.Spectrum.LineWidth;
    if (aDbQuery.JsonData.hasOwnProperty("points")) points = aDbQuery.JsonData.points;
    
    // Compose NMR Spectrum
    var doc = new Document();
    Application.lockDocument(doc);

    var spec = new NMRSpectrum({
        1: {nucleus: spectrumNucleus, frequency: SpectrometerFrequency, from_ppm: spectrumFrom, to_ppm: spectrumTo, size: points.length},
        filename: 'dbQueryNmrSpectrum',
        origin: 'dbQueryNmrSpectrum',
        title: ''
        });

    nmr.beginModification(spec);
    spec.setReal("all", points);
    spec.fitToHeight();
    nmr.endModification(spec);

    var clonedSpec = DBPlugin.clonePageItem(spec);

    Application.unlockDocument();
    doc.destroy();
    
    return clonedSpec;
};

// Convert the given raw data stream into CanvasItem using Mnova serialization 
dbPrivate.rawdataToCanvasitem = function(aText,aFormat) {
    "use strict";
    var pageItem, tempFile, doc = new Document();

    // Lock Document
    Application.lockDocument(doc);

    // Load CanvasItem
    tempFile = Dir.temp() + "/rawdataToCanvasitem-" +  Application.createUuid().replace(/[\{\}]/g, "") + aFormat;
    dbCreateTextFile(tempFile,aText);
    var filter = aFormat;
    if (aFormat == ".csv"){
        filter = "MS CSV File (*.csv *.txt)";
    }
    if (serialization.importFile(tempFile, filter, doc)) {
        pageItem = doc.item(0);
    }
    File.remove(tempFile);

    var clonedSpec = DBPlugin.clonePageItem(pageItem);

    // Unlock Document
    Application.unlockDocument();
    doc.destroy();

    return clonedSpec;
};

/*
    Compose a NMR 1D Spectrum in Xml format : 
     aNucleus : 1H, 13C
     aSpectrumFrom :  lowest frequency in ppm
     aSpectrumTo : highest frequency in ppm
     aSpectralSize : spectrum points number
     aSpectrumFrequency : spectrometer frecuency
     aLineWidth : pulse with
     aPeakList : matrix of 3 dimensions {pos,width,intensity}
*/
dbPrivate.xmlNmr1DSpectrum = function(aNucleus,aSpectrumFrom,aSpectrumTo,aSpectralSize,aSpectrumFrequency,aLineWidth,aPeakList) {
    var xml, i, peak;
  
    // Compose NMR Xml
    var peakSim = new TNMRPeakSpectrumSim(aNucleus);
    if (aSpectrumFrom) peakSim.fSpectrum.from = aSpectrumFrom;
	if (aSpectrumTo) peakSim.fSpectrum.to = aSpectrumTo;
	if (aSpectralSize) peakSim.fSpectrum.nPoints = aSpectralSize;
	if (aSpectrumFrequency) peakSim.fSpectrum.frequency = aSpectrumFrequency;
    if (aLineWidth) peakSim.fSpectrum.lineWidth = aLineWidth;

    // Compose NMR Xml Peaks
    peakSim.fPeaks = [];
    if (!aPeakList){
        return xml;
    }
    
    var peakMaxPos = peakSim.fSpectrum.to;
    for (i = 0; i < aPeakList.length; i++) {
        peak = aPeakList[i];
        if (peak.length == 3){
            peakSim.fPeaks.push({'heigth': peak[2], 'dim': [{'shift': parseFloat(peak[0]), 'width': peak[1]}]});
            if (parseFloat(peak[0]) > peakMaxPos) peakMaxPos = peak[0];
        }
    }
    peakSim.fSpectrum.to = parseInt(peakMaxPos * 1.20);

    xml = peakSim.toXML();
    return xml;
};

// ---------------------------------------------------------------------------------------------------------------------
// dbTest contains functions for testing and debugging which are not used anywhere outside this script.
// ---------------------------------------------------------------------------------------------------------------------

var dbTest = {};

// Print 1H assignments for the active molecule.
dbTest.testAssignments = function () {
    "use strict";
    var moleculePlugin = new MoleculePlugin(Application.molecule),
        mol = new Molecule(moleculePlugin.activeMolecule());
    if (mol.isValid() === false) {
        throw "Invalid Molecule";
    }
    print(dbGetAssignments(mol, "1H"));
};

// Build db items from the current current and print the result to the debug console.
dbTest.testMakeDBItems = function () {
    "use strict";
    var i,
        items = dbMakeDBItems(DBPlugin.client, DBDefinition.defaultDefinition("Test Database"), true, true);
    for (i = 0; i < items.length; i++) {
        print("Item " + i + ":\n" + items[i].toString());
    }
};

dbTest.testMolecule = function (aMolecule) {
    "use strict";
    var i, paramNames, sdfNames;
    //print(item);
    print("    molFile = " + aMolecule.getMolfile());
    print("    molecularFormula = " + aMolecule.molecularFormula());
    print("    molecularWeight = " + aMolecule.molecularWeight());
    paramNames = aMolecule.paramNames();
    print("    " + paramNames.length +  " parameters:");
    for (i = 0; i < paramNames.length; i++) {
        print("        " + paramNames[i] + ": " + aMolecule.getParam(paramNames[i]));
    }
    sdfNames = aMolecule.sdfNames();
    print("    " + sdfNames.length +  " sdf tags:");
    for (i = 0; i < sdfNames.length; i++) {
        print("        " + sdfNames[i] + ": " + aMolecule.getSDFData(sdfNames[i]));
    }
    print("    Assignmnents: " + dbGetAssignmentsAsText(aMolecule));
    print("    Assignments 1H: " + dbGetAssignments(aMolecule, "1H"));
    print("    Assignments 13C: " + dbGetAssignments(aMolecule, "13C"));
};

dbTest.testNMRSpectrum = function (aSpectrum) {
    "use strict";
    var i,
        //visibleRegion = getVisibleRegionPPM(aSpectrum),
        //totalRegion = getTotalRegionPPM(aSpectrum),
        paramNames = aSpectrum.paramNames();
    //print("    visible region Dim1: from=" + visibleRegion.from(1) + " to=" + visibleRegion.to(1) + " Dim2: from=" + visibleRegion.from(2) + " to=" + visibleRegion.to(2));
    //print("    total region Dim1: from=" + totalRegion.from(1) + " to=" + totalRegion.to(1) + " Dim2: from=" + totalRegion.from(2) + " to=" + totalRegion.to(2));
    print("    " + paramNames.length +  " parameters:");
    for (i = 0; i < paramNames.length; i++) {
        print("      " + paramNames[i] + ": " + aSpectrum.getParam(paramNames[i]));
    }
    print("    dimCount: " + aSpectrum.dimCount);
    print("    nmrPeaks1D: " + getNMRPeaks(aSpectrum, 1));
    print("    nmrPeaks2D: " + getNMRPeaks(aSpectrum, 2));
    print("    nmrMultiplets: " + getNMRMultiplets(aSpectrum, 1));
    print("    nmrOriginalFormat: " + aSpectrum.originalFormat);
    print("    integrals1D: " + getIntegrals(aSpectrum, 1));
    print("    integrals2D: " + getIntegrals(aSpectrum, 2));
    print("    verification result: " + getVerificationResult(aSpectrum).testsResultsAsText());
    print("    nmrPoints1D: " + dbGetNMRPoints(aSpectrum, 1));
    print("    nmrPoints2D: " + dbGetNMRPoints(aSpectrum, 2));
};

dbTest.testMassItem = function (aMassItem) {
    "use strict";
    var i, msInjection, f, msFunction, t, msTrace, p, plot, field,
        ms = aMassItem;
    print("    Dataset File = " + ms.datasetFileName);
    print("    Info valid = " + ms.info.valid);
    if (ms.info.valid) {
        print("    Injections count = " + ms.info.injections.count);
        for (i = 0; i < ms.info.injections.count; i++) {
            msInjection = ms.info.injections.getItem(i);
            print("      Injection " + i + " Name = '" + msInjection.name + "'");
            print("        Functions count = " + msInjection.functions.count);
            for (f = 0; f < msInjection.functions.count; f++) {
                print("          Function " + f + " Name = '" + msInjection.functions.getItem(f).name + "'");
            }
            print("        Traces count = " + msInjection.traces.count);
            for (t = 0; t < msInjection.traces.count; t++) {
                print("          Trace " + t + " Name = '" + msInjection.traces.getItem(t).name + "'");
            }
        }
    }
    print("    " + MassMetaDataHandler.fields.length + " fields:");
    for (f = 0; f < MassMetaDataHandler.fields.length; f++) {
        field = MassMetaDataHandler.fields[f];
        print("      " + field.label + " = '" + aMassItem.getMetaData(field.propertyName) + "'");
        // + "' (Default = '" + field.getDefaultValue(aMassItem, field) + "')");
    }
    print("    " + Object.keys(ms.parameters).length + " Parameters:");
    for (p = 0; p < Object.keys(ms.parameters).length; p++) {
        print("      Parameter " + p + " " + Object.keys(ms.parameters)[p] + ": " + ms.parameters[Object.keys(ms.parameters)[p]]);
    }
    print("    " + ms.plotsCount + " plots:");
    for (p = 0; p < ms.plotsCount; p++) {
        plot = ms.plot(p);
        print("        Plot " + p + " Type=" + plot.type + " PlotId =" + plot.id + " Valid=" + plot.valid);
        print("          injectionIndex=" + plot.injectionIndex  + "  functionIndex=" + plot.functionIndex + " traceIndex=" + plot.traceIndex);
        if (ms.info.valid && plot.injectionIndex >= 0) {
            msInjection = ms.info.injections.getItem(plot.injectionIndex);
            print("          Injection " + plot.injectionIndex + ": " + msInjection.name);
            if (plot.functionIndex >= 0) {
                msFunction = msInjection.functions.getItem(plot.functionIndex);
                print("          Function " + plot.functionIndex + ": " + msFunction.name);
            }
            if (plot.traceIndex >= 0) {
                msTrace = msInjection.traces.getItem(plot.traceIndex);
                print("          Trace " + plot.traceIndex + ": " + msTrace.name);
            }
        }
        if (plot.type === MassItem.DataTypes.Chromatogram) {
            // plot type === 1
            print("          Chromatogram Description: '" + plot.description + "'");
            print("          Chromatogram Type: " + dbMassChromatogramType(ms, p));
            print("          RT Prefix: " + dbRTFieldPrefix(ms, p));
            print("          Params: " + plot.parameters);
            //print(plot.peaks);
        } else if (plot.type === MassItem.DataTypes.Spectrum) {
            // plot type === 2
            print("          Mass Spectrum " + " Description='" + plot.description + "'");
            print("          Chromatogram Type: " + dbMassChromatogramType(ms, p)); // Should be null
            print("          RT Prefix: " + dbRTFieldPrefix(ms, p));                // Should be null
            print("           foreTimes:");
            for (t = 0; t < plot.times.foreTimes.length; t++) {
                print("             " + t + ": " + plot.times.foreTimes[t].from + ".." + plot.times.foreTimes[t].to);
            }
            print("           backTimes:");
            for (t = 0; t < plot.times.backTimes.length; t++) {
                print("             " + t + ": " + plot.times.backTimes[t].from + ".." + plot.times.backTimes[t].to);
            }
        } else {
            print("           Unknown mass plot type" + p + " PlotId = '" + plot.id[p] + "' Description='" + plot.description + "' valid=" + plot.valid);
        }
    }
};

dbTest.testElViSSpectrum = function (aSpectrum) {
    "use strict";
    print("    Original Format: " + aSpectrum.originalFormat);
    print("    Spectrum Title: " + aSpectrum.title);
    print("    Spectrum Comment: " + aSpectrum.comment);
    print("    Spectrum Count: " + aSpectrum.specCount);
    print("    Spectrum Index: " + aSpectrum.curSpecIndex);
    print("    Points: " + aSpectrum.count());
    print("    Peaks: " + dbGetElViSPeaks(aSpectrum));
    print("    Points: " + dbGetElViSPoints(aSpectrum));
};

// Print information about the page item with the given uuid to the debug console.
dbTest.testPageItem = function (aUuid) {
    "use strict";
    var item = dbGetItem(aUuid);
    //print(item);
    if (item.name === "Molecule") {
        this.testMolecule(new Molecule(item));
    } else if (item.name === "NMR Spectrum") {
        this.testNMRSpectrum(new NMRSpectrum(item));
    } else if (item.name === "Mass Spectrum") {
        this.testMassItem(new MassItem(item));
    } else if (item.name === "ElViS Spectrum") {
        this.testElViSSpectrum(new ElViSSpectrum(item));
    }

    print("    Text = " + getText(item));
    print("    Notes = " + getPageNotes(item));
	
	print("    Metadata: ");
	var metadata = ['mndb.source'];
	for (var i = 0; i < metadata.length; i++) {
		if (item.hasMetaData(metadata[i])){
			print("    	" + metadata[i] + " = " + item.getMetaData(metadata[i]));
		}
	}

};

// Print information about page items in the current document to the debug console
dbTest.testPageItemsAll = function () {
    "use strict";
    var i,
        dw = mainWindow.activeWindow();
    for (i = 0; i < dw.itemCount(); i++) {
        print("  Item " + i + ": " + dw.item(i) + " Name='" + dw.item(i).name + " Uuid='" + dw.item(i).uuid + "' Type='" + dw.item(i).type + "' Subtype='" + dw.item(i).subtype + "'");
        this.testPageItem(dw.item(i).uuid);
    }
    print(dw.itemCount() + " items on " + dw.pageCount() + " pages.");
};

// Print information about page items in the current document to the debug console
dbTest.testPageItemsSelection = function () {
    "use strict";
    var i, items,
        dw = mainWindow.activeWindow();
    items = dw.selection();
    for (i = 0; i < items.length; i++) {
        print("  Item " + i + ": " + items[i] + " Name='" + items[i].name + " Uuid='" + items[i].uuid + "' Type='" + items[i].type + "' Subtype='" + items[i].subtype + "'");
        this.testPageItem(items[i].uuid);
    }
    print(items.length + " items in selection.");
};

// Print a short listing of all items in to the current document to the debug console
dbTest.printItems = function () {
    "use strict";
    var i,
        dw = mainWindow.activeWindow();
    if (!dw) {
        return;
    }
    for (i = 0; i < dw.itemCount(); i++) {
        print(" Item " + i + " with Uuid " +  dw.item(i).uuid + " : " + dw.item(i));
    }
    print(dw.itemCount() + " items on " + dw.pageCount() + " pages.");
};
/*
dbTest.printEval = function(code)
{
    print(code + " = " + eval(code));
}
dbTest.printConstants = function() 
{
    var i, peaktypes, gsd
    var peaktypes = ['Compound', 'Artifact', 'Impurity', 'Solvent', 'CS_Ref', 'Quantification_Ref'];
    for (i in peaktypes) printEval("Peak." + peaktypes[i]);
    var gsdpeaktypes = ['Compound', 'Artifact', 'Impurity', 'Solvent', 'SReference', 'QReference'];
    for (i in gsdpeaktypes) printEval("GSDPeak.Types." + gsdpeaktypes[i]);
    var gsdpeakflags = ['C13Satellite', 'Hidden', 'Labile', 'None', 'Rotational', 'Weak'];
    for (i in gsdpeakflags) printEval("GSDPeak.Flags." + gsdpeakflags[i]);
}
*/

// ---------------------------------------------------------------------------------------------------------------------
// End of file.

